#include "xmireader.h"

XMIReader::XMIReader() : AbstractReader()
{
    this->_dataTypes->insert("http://schema.omg.org/spec/UML/2.1/uml.xml#Integer", "int", true);
    this->_dataTypes->insert("http://schema.omg.org/spec/UML/2.1/uml.xml#Any", "any", true);
    this->_dataTypes->insert("http://schema.omg.org/spec/UML/2.1/uml.xml#Bool", "bool", true);
    this->_dataTypes->insert("http://schema.omg.org/spec/UML/2.1/uml.xml#Char", "char", true);
    this->_dataTypes->insert("http://schema.omg.org/spec/UML/2.1/uml.xml#UChar", "uchar", true);
    this->_dataTypes->insert("http://schema.omg.org/spec/UML/2.1/uml.xml#Byte", "byte", true);
    this->_dataTypes->insert("http://schema.omg.org/spec/UML/2.1/uml.xml#Short", "short", true);
    this->_dataTypes->insert("http://schema.omg.org/spec/UML/2.1/uml.xml#UShort", "ushort", true);
    this->_dataTypes->insert("http://schema.omg.org/spec/UML/2.1/uml.xml#String", "string", true);
}

void XMIReader::setIsWMI(bool isWMI)
{
    this->_isWMI = isWMI;
}

void XMIReader::run()
{
    QString errorStr;
    int errorLine;
    int errorColumn;
    QDomDocument doc;

    QFile file(this->_filePath);

    // reading file into dom
    if (!doc.setContent(&file, true, &errorStr, &errorLine, &errorColumn)) {
        QMessageBox::warning(
                0,
                QObject::tr("DOM Parser"),
                QObject::tr("Parse error at line %1, column %2:\n%3")
                .arg(errorLine)
                .arg(errorColumn)
                .arg(errorStr)
                );
    }

    file.close();

    UmlItem *rootDir = 0;

    // Root-Element
    QDomElement root = doc.documentElement();

    // Model-Element
    QDomElement model = root.firstChildElement("Model");


    switch(this->projectType(root)) {
    default :
            this->_project = new UmlProject();
    break;
    case UmlProject::ClassDiagramm :
    this->_project = new UmlClassDiagramm();
    break;
}

    this->parseCustomDataTypes(model);
    rootDir = this->parseElements(model.firstChildElement("packagedElement"));
    this->parseRelations(model.firstChildElement("packagedElement"));

    this->_project->setPath(file.fileName());
    this->_project->setName(model.attribute("name"));
    this->_project->setUmlItemRoot(static_cast<UmlDirItem*>(rootDir));
    this->_project->setDataTypes(this->_dataTypes);

    qDebug() << "finished reading";
    emit this->finished(this->_project, this->_dataTypes);
}

UmlProject::ProjectType XMIReader::projectType(QDomElement root)
{
    return UmlProject::ClassDiagramm;
}

void XMIReader::parseCustomDataTypes(QDomElement root)
{
    QDomElement element = root.firstChildElement("packagedElement");
    while (!element.isNull()) {

        QString elementType = element.attribute("type");
        if (elementType == "uml:DataType" || elementType == "uml:PrimitiveType") {
            this->_dataTypes->insert(element.attribute("id"), element.attribute("name"));
        }

        element = element.nextSiblingElement("packagedElement");
    }
}

UmlGraphicsItem* XMIReader::parseElements(QDomElement root)
{
    // parsing package
    if (root.attribute("type") == "uml:Package") {
        UmlDirItem *dir = new UmlDirItem(this->_project);
        dir->setId(root.attribute("id"));
        dir->setText(root.attribute("name"));

        for (QDomNode child = root.firstChild(); !child.isNull(); child = child.nextSibling()) {
            UmlGraphicsItem* item = this->parseElements(child.toElement());

            if (item) {
                dir->appendChild(item);
            }
        }

        return dir;
    }

    // parsing classes
    else if (root.attribute("type") == "uml:Class") {
        UmlClassItem *classItem = new UmlClassItem(this->_project);
        classItem->setId(root.attribute("id"));
        classItem->setText(root.attribute("name"));
        classItem->setIsAbstract(root.attribute("isAbstract") == "true");

        if (this->_isWMI) {
            QPointF pos(QVariant(root.attribute("x_pos")).toReal(), QVariant(root.attribute("y_pos")).toReal());
            classItem->setPos(pos);
            classItem->setShowAtts(QVariant(root.attribute("show_atts")).toBool());
            classItem->setShowMeths(QVariant(root.attribute("show_meths")).toBool());
        }

        this->parseClass(root, classItem);

        for (QDomElement child = root.firstChild().toElement(); !child.isNull(); child = child.nextSibling().toElement()) {
            if (child.attribute("xmi:type") == "uml:Property") {
                this->_ownedAttributes[child.attribute("xmi:id")] = child;
            }
        }

        this->_relationalItems[classItem->id()] = classItem;

        return classItem;
    }

    // parsing interfaces
    else if (root.attribute("type") == "uml:Interface") {
        UmlClassItem *interface = new UmlClassItem(this->_project);
        interface->setIsInterface(true);
        interface->setId(root.attribute("id"));
        interface->setText(root.attribute("name"));

        if (this->_isWMI) {
            QPointF pos(QVariant(root.attribute("x_pos")).toReal(), QVariant(root.attribute("y_pos")).toReal());
            interface->setPos(pos);
        }

        this->parseClass(root, interface);
        this->_relationalItems[interface->id()] = interface;

        return interface;
    }

    return 0;
}

void XMIReader::parseClass(QDomElement root, UmlClassItem *classItem)
{
    QDomElement element = root.firstChild().toElement();

    while (!element.isNull()) {

        // parsing attributes
        if (element.tagName() == "ownedAttribute" && !element.hasAttribute("association")) {
            ClassAttribute *att = new ClassAttribute();
            att->setText(element.attribute("name"));
            att->setVisibility(element.attribute("visibility"));
            att->setReturnType(this->_dataTypes->labelById(element.firstChildElement("type").attribute("href")));
            classItem->appendAtt(att);
        }

        else if (element.tagName() == "ownedAttribute" && element.hasAttribute("association")) {
            this->_ownedAttributes[element.attribute("id")] = element;
        }

        // parsing operations
        else if (element.tagName() == "ownedOperation") {
            ClassMethod *meth = new ClassMethod();
            meth->setText(element.attribute("name"));
            meth->setVisibility(element.attribute("visibility"));

            QDomElement ownedParameter = element.firstChildElement("ownedParameter");

            while (!ownedParameter.isNull()) {

                // param data type
                if (ownedParameter.attribute("type") == "uml:Parameter" && ownedParameter.attribute("direction") == "in") {
                    QDomElement type = ownedParameter.firstChildElement("type");
                    ClassAttribute *att = new ClassAttribute();
                    att->setText(ownedParameter.attribute("name"));

                    att->setReturnType(this->_dataTypes->labelById(type.attribute("href")));
                    meth->addAtt(att);
                }

                // return value
                if (ownedParameter.attribute("direction") == "return") {
                    QDomElement type = ownedParameter.firstChildElement("type");

                    meth->setReturnType(this->_dataTypes->labelById(type.attribute("href")));
                }

                ownedParameter = ownedParameter.nextSiblingElement();
            }

            classItem->appendMeth(meth);
        }

        element = element.nextSiblingElement();
    }

}

void XMIReader::parseRelations(QDomElement root)
{
    if (root.attribute("type") == "uml:Package") {
        for (QDomNode child = root.firstChild(); !child.isNull(); child = child.nextSibling()) {
            this->parseRelations(child.toElement());
        }
    }

    else if (root.attribute("type") == "uml:Class") {
        for (QDomNode child = root.firstChild(); !child.isNull(); child = child.nextSibling()) {

            QDomElement element = child.toElement();

            if (element.tagName() == "generalization") {
                UmlRelation *relation = new UmlRelation(UmlRelation::UmlGeneralizationRelationType);

                QString parentId = element.attribute("general");
                QString childId = root.attribute("id");

                UmlRelationalItem *parentItem = this->_relationalItems[parentId];
                UmlRelationalItem *childItem = this->_relationalItems[childId];

                relation->setItems(childItem, parentItem);
            }
        }
    }

    else if (root.attribute("type") == "uml:Realization") {
        UmlRelation *relation = new UmlRelation(UmlRelation::UmlRealizationRelationType);

        QString supplierId = root.attribute("supplier");
        QString clientdId = root.attribute("client");

        UmlRelationalItem *supplierItem = this->_relationalItems[supplierId];
        UmlRelationalItem *clientItem = this->_relationalItems[clientdId];

        relation->setItems(clientItem, supplierItem);
    }

    else if (root.attribute("type") == "uml:Association") {
        QString refId1 = root.firstChildElement("memberEnd").attribute("idref");
        QString refId2 = root.lastChildElement("memberEnd").attribute("idref");

        QDomElement memberTag1 = this->_ownedAttributes[refId1];
        QDomElement memberTag2 = this->_ownedAttributes[refId2];

        UmlRelationalItem *item1 = this->_relationalItems[memberTag1.firstChildElement("type").attribute("idref")];
        UmlRelationalItem *item2 = this->_relationalItems[memberTag2.firstChildElement("type").attribute("idref")];

        QString lowerValue2 = memberTag1.firstChildElement("lowerValue").attribute("value");
        QString upperValue2 = memberTag1.firstChildElement("upperValue").attribute("value");

        QString lowerValue1 = memberTag2.firstChildElement("lowerValue").attribute("value");
        QString upperValue1 = memberTag2.firstChildElement("upperValue").attribute("value");

        UmlCardinality c1(lowerValue1, upperValue1);
        UmlCardinality c2(lowerValue2, upperValue2);

        UmlRelation *relation = 0;

        if (memberTag1.attribute("aggregation") == "shared" && memberTag2.attribute("aggregation") == "none") {
            relation = new UmlRelation(UmlRelation::UmlAggregationRelationType);
            relation->setItems(item2, item1);
        }

        else if (memberTag1.attribute("aggregation") == "none" && memberTag2.attribute("aggregation") == "shared") {
            relation = new UmlRelation(UmlRelation::UmlAggregationRelationType);
            relation->setItems(item1, item2);
        }

        else if (memberTag1.attribute("aggregation") == "composite" && memberTag2.attribute("aggregation") == "none") {
            relation = new UmlRelation(UmlRelation::UmlCompositionRelationType);
            relation->setItems(item1, item2);
        }

        else if (memberTag1.attribute("aggregation") == "none" && memberTag2.attribute("aggregation") == "composite") {
            relation = new UmlRelation(UmlRelation::UmlCompositionRelationType);
            relation->setItems(item2, item1);
        }

        else {
            relation = new UmlRelation(UmlRelation::UmlAssociationRelationType);
            relation->setItems(item1, item2);
        }

        relation->setText(root.attribute("name"));
        relation->setRoleItem1(memberTag2.attribute("name"));
        relation->setRoleItem2(memberTag1.attribute("name"));
        relation->setCardinalityItem1(c1);
        relation->setCardinalityItem2(c2);
    }
}
